
let toolsPublic = require('./tools-public.js')
let media = require('./tools-web-media.js')
let polyfill = require('./tools-web-polyfill.js')

/**
 * 下载文件 - a标签（跨域时download属性改名无效）
 * @param {String} options.from 源地址
 * @param {String} options.to 目标地址/文件名
 */
const downloadFileByA = (options) => {
  let from = ''
  let to = ''
  if (toolsPublic.typeOf(options).toLowerCase() === 'string') {
    from = options
  } else{
    from = (options || {}).from
    to = ((options || {}).to || '').split(/[\\|\/]/g).pop()
  }
  // 方法1
  let ele = document.createElement('a')
  ele.target = '_blank'
  ele.download = to
  ele.style.display = 'none'
  ele.href = from
  document.body.appendChild(ele)
  ele.click()
  document.body.removeChild(ele)
  // 方法2
  // let ele = document.createElement('a')
  // let evt = document.createEvent('MouseEvents') // 创建鼠标事件对象
  // evt.initEvent('click', false, false) // 初始化事件对象
  // ele.target = '_blank'
  // ele.href = from // 设置下载地址
  // ele.download = to // 设置下载文件名
  // ele.dispatchEvent(e)
}

/**
 * 下载文件 - a标签V2（跨域时download属性改名生效）
 * @param {String} options.from 源地址
 * @param {String} options.to 目标地址/文件名
 */
const downloadFileByAV2 = (options) => {
  let from = ''
  let to = ''
  if (toolsPublic.typeOf(options).toLowerCase() === 'string') {
    from = options
  } else{
    from = (options || {}).from
    to = ((options || {}).to || '').split(/[\\|\/]/g).pop()
  }
  let xhr = new XMLHttpRequest()
  xhr.open('GET', from, true)
  xhr.responseType = 'blob'
  xhr.onload = (e) => {
    let url = window.URL.createObjectURL(xhr.response)
    let ele = document.createElement('a')
    ele.target = '_blank'
    ele.href = url
    ele.download = to
    ele.style.display = 'none'
    document.body.appendChild(ele)
    ele.click()
    document.body.removeChild(ele)
  }
  xhr.send()
}

/**
 * 下载文件 - iframe标签
 * @param {String} from 源地址
 */
const downloadFileByIframe = (options) => {
  let from = ''
  if (toolsPublic.typeOf(options).toLowerCase() === 'string') {
    from = options
  } else{
    from = (options || {}).from
  }
  let ele = document.createElement('iframe')
  ele.style.display = 'none'
  ele.src = from
  document.body.appendChild(ele)
}

/**
 * 下载文件 - form标签
 * @param {String} from 源地址
 */
const downloadFileByForm = (options) => {
  let from = ''
  if (toolsPublic.typeOf(options).toLowerCase() === 'string') {
    from = options
  } else{
    from = (options || {}).from || ''
  }
  let ele = document.createElement('form')
  ele.style.display = 'none'
  ele.setAttribute ? ele.setAttribute('method', 'get') : (ele.method = 'get')
  ele.setAttribute ? ele.setAttribute('target', '_blank') : (ele.target = '_blank')
  ele.setAttribute ? ele.setAttribute('action', from) : (ele.action = from)
  document.body.appendChild(ele)
  ele.submit()
}

/**
 * 保存数据流到文件 - blob
 * @param {Blob} data 数据
 * @param {String} fileName 文件名
 */
const saveFileByBlob = (options) => {
  let {data, fileName} = options || {}
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(data, fileName)
  } else {
    window.URL = window.URL || window.webkitURL

    // 方案一
    // let ele = document.createElement('a')
    // ele.target = '_blank'
    // ele.href = window.URL.createObjectURL(data)
    // ele.download = fileName
    // ele.click()
    // window.URL.revokeObjectURL(ele.href) // 用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象

    // 方案二 20201010修复Safari的(WebkitBlobResource error 1.)问题
    let url = window.URL.createObjectURL(data)
    downloadFileByA({from: url, to: fileName})
  }
}

/**
* 控制台调试Vue项目:定位Vue组件
* @param {Object} vueObj vue实例（搜索会在该实例下进行）
* @param {String} className 要查找的组件的根类名
* @returns {Object} 返回一个包含了目标组件路径的_uid数组，以及组件实例
*/
const searchVueComponent = (vueObj, className) => {
  let result = {path: [], component: null}
  vueObj.$children.forEach((v, i) => {
    if (!v) return
    if (v.$el && v.$el.classList && v.$el.classList.contains(className)) {
      let cmpPath = []
      let curCmp = v
      while (curCmp) {
        cmpPath.unshift(curCmp._uid)
        curCmp = curCmp.$parent
      }
      result.path = cmpPath
      result.component = v
    } else if ((v.$children || []).length) {
      searchVueComponent(v, className)
    }
  })
  return result
}

/**
 * 下载文件
 * @param {String} from 源地址
 * @param {String} to 目标地址/文件名
 * @param {Function} progressCallback 进度回调函数
 * @param {Object} config 其它配置
 */
const downloadFile = (options) => {
  let {from, to, progressCallback, config} = options || {}
  let responseType = (config || {}).responseType || 'blob'
  let fileName = (to || '').split(/[\\|\/]/g).pop()
  return new Promise((resolve, reject) => {
      toolsPublic.downloadInstance({
        url: from,
        responseType,
        onDownloadProgress: progressCallback
      }).request(config || {}).then((res) => {
        let data = res.data
        // if (responseType === 'arraybuffer') {
        //   data = String.fromCharCode.apply(null, new Uint8Array(data))
        // }
        let blob = new Blob([data], {type: 'application/octet-stream'})
        saveFileByBlob({data: blob, fileName})
        resolve()
      }).catch((err) => {
        console.log(err)
        reject('error')
      })
  }).catch((err) => {
      console.log(err)
      return 'error'
  })
}

/**
 * Number转千位分隔符字符串
 * @param {Number} num
 * @returns {String}
 */
const formatNumber = (num) => {
  if (isNaN(parseFloat(num)) || !isFinite(num)) {
    return num
  }
  let result = ''
  if (window.Intl && Intl.NumberFormat) {
    result = new Intl.NumberFormat('en-US', {}).format(num)
  } else if (Number.toLocaleString) {
    result = num.toLocaleString('en-US')
  } else {
    let temp = String(num).split('.')
    result = temp[0].replace(/(\d)(?=(\d{3})+$)/g, '$1,')
    result += temp[1] ? ('.' + temp[1]) : ''
  }
  return result
}

/**
 * 执行带浏览器前缀的方法
 * @param {Object} element
 * @param {String} method
 */
const runPrefixMethod = (element, method) => {
  let usablePrefixMethod
  ['webkit', 'moz', 'ms', 'o', ''].forEach((prefix) => {
    if (usablePrefixMethod) return
    if (prefix === '') {
      // 无前缀，方法首字母小写
      method = method.slice(0,1).toLowerCase() + method.slice(1)
    }
    let typePrefixMethod = typeof element[prefix + method]
    if (typePrefixMethod + '' !== 'undefined') {
      if (typePrefixMethod === 'function') {
        usablePrefixMethod = element[prefix + method]()
      } else {
        usablePrefixMethod = element[prefix + method]
      }
    }
  })
  return usablePrefixMethod
}

/**
 * 绑定带浏览器前缀的事件
 * @param {Object} element 目标DOM元素
 * @param {String} eventName 事件名称
 * @param {Function} callback 事件回调函数
 * @param {Boolean} capture 是否在捕获阶段触发
 */
const onPrefixEvent = (element, eventName, callback, capture = false) => {
  if (!element || (typeof callback !== 'function')) return
  let usablePrefixEvent
  ['webkit', 'moz', 'ms', 'o', ''].forEach((prefix) => {
    if (usablePrefixEvent) return
    let typePrefixEvent = typeof element['on' + prefix + eventName]
    if (typePrefixEvent + '' !== 'undefined') {
      usablePrefixEvent = prefix + eventName
      element.addEventListener(prefix + eventName, callback, capture)
    }
  })
  return usablePrefixEvent
}

/**
 * 解绑带浏览器前缀的事件
 * @param {Object} element 目标DOM元素
 * @param {String} eventName 事件名称
 * @param {Function} callback 事件回调函数
 * @param {Boolean} capture 是否在捕获阶段触发
 */
const offPrefixEvent = (element, eventName, callback, capture = false) => {
  if (!element || (typeof callback !== 'function')) return
  ['webkit', 'moz', 'ms', 'o', ''].forEach((prefix) => {
    let typePrefixEvent = typeof element['on' + prefix + eventName]
    if (typePrefixEvent + '' !== 'undefined') {
      element.removeEventListener(prefix + eventName, callback, capture)
    }
  })
}

/**
 * 利用a标签自动解析URL
 * @param {String} url 网址
 */
const parseURLByA = (url = '') => {
  let a =  document.createElement('a')
  a.href = url
  return {
    source: url,
    protocol: a.protocol.replace(':', ''),
    host: a.hostname,
    port: a.port,
    query: a.search,
    params: (() => {
      let ret = {}
      let seg = a.search.replace(/^\?/,'').split('&')
      let len = seg.length
      let s
      for (let i = 0; i < len; i++) {
        if (!seg[i]) { continue }
        s = seg[i].split('=')
        ret[s[0]] = s[1]
      }
      return ret
    })(),
    file: (a.pathname.match(/\/([^\/?#]+)$/i) || [, ''])[1],
    hash: a.hash.replace('#', ''),
    path: a.pathname.replace(/^([^\/])/, '/$1'),
    relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [, ''])[1],
    segments: a.pathname.replace(/^\//, '').split('/')
  }
}

/**
 * 滚动条垂直滚动
 * @param {Object} element 目标DOM元素
 * @param {Number} from 起点
 * @param {Number} to 终点
 * @param {Number} duration 滚动时长
 * @param {Function} endCallback 滚动结束后的回调函数
 */
const scrollTop = (element, from = 0, to, duration = 500, endCallback) => {
  polyfill.polyfillRequestAnimationFrame()
  const difference = Math.abs(from - to)
  const step = Math.ceil(difference / duration * 50)
  scroll(from, to, step)
  function scroll(start, end, step) {
    if (start === end) {
      endCallback && endCallback()
      return
    }
    let d = (start + step > end) ? end : start + step
    if (start > end) {
      d = (start - step < end) ? end : start - step
    }
    if (element === window) {
      window.scrollTo(d, d)
    } else {
      element.scrollTop = d
    }
    window.requestAnimationFrame(() => scroll(d, end, step))
  }
}

/**
 * 判断是否处在全屏状态
 * @returns {Boolean}
 */
const isFullscreen = () => {
  return !!(runPrefixMethod(document, 'FullscreenElement')
  || runPrefixMethod(document, 'FullScreen')
  || runPrefixMethod(document, 'IsFullScreen'))
}

/**
 * 判断是否支持全屏方法
 * @returns {Boolean}
 */
const isFullscreenEnabled = () => {
  return !!(runPrefixMethod(document, 'FullscreenEnabled'))
}

/**
 * 判断是否微信浏览器
 * @returns {Boolean}
 */
const isWeixinBrowser = () => {
  return /micromessenger/.test(navigator.userAgent.toLowerCase())
}

/**
 * 判断是否是移动端浏览器
 * @returns {Boolean}
 */
const isMobileBrowser = () => {
  let result = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|SymbianOS|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
  return !!result
}

/**
 * 判断浏览窗口是否是横屏
 * @returns {Boolean}
 */
const screenIsLandscape = () => {
  let result = false
  if ('orientation' in window) {
    result = (window.orientation === 90 || window.orientation === -90 )
  } else {
    let width = document.documentElement.clientWidth
    let height = document.documentElement.clientHeight
    result = (width >= height)
  }
  return result
}

/**
 * 判断浏览窗口是否是竖屏
 * @returns {Boolean}
 */
const screenIsPortrait = () => {
  return !screenIsLandscape()
}

/**
 * base64解密处理器
 * @param {String} base64Str
 */
const base64Decode = (base64Str) => {
  let result = ''
  if (base64Str) {
    try {
      result = atob(base64Str)
    } catch (err) {
      console.log(err)
      result = ''
    }
  }
  return result
}

/**
 * base64转utf8
 * @param {String} base64Str
 */
const base642utf8 = (base64Str) => {
  return decodeURIComponent(escape(window.atob(base64Str)))
}

/**
 * utf8转base64
 * @param {String} utf8Str
 */
const utf82base64 = (utf8Str) => {
  return window.btoa(unescape(encodeURIComponent(utf8Str)))
}

/**
 * 将数据字符串转换为文件对象
 * @param {String} dataUrl
 * @param {String} type 1(file对象)/2(blob对象)
 * @param {String} fileName
 * @returns {File/Blob}
 */
const dataUrl2File = (dataUrl, type, fileName) => {
  let arr = dataUrl.split(',')
  let mime = arr[0].match(/:(.*?);/)[1]
  let bstr = atob(arr[1])
  let n = bstr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  if (type === 1) {
    // 转换成file对象
    return new File([u8arr], fileName, { type: mime })
  } else {
    // 转换成blob对象
    return new Blob([u8arr], { type: mime })
  }
}

/**
 * File转换DataURL
 * @param {Object} file 待转换的File对象
 * @returns {Promise} Promise对象，成功后返回DataURL地址
 */
const File2dataUrl = (file) => {
  return new Promise((resolve, reject) => {
    let fr = new FileReader()
    fr.addEventListener('load', () => {
      resolve(fr.result)
    })
    fr.addEventListener('error', (err) => {
      reject(err)
    })
    fr.readAsDataURL(file)
  })
}

/**
 * Image DOM元素转换DataURL
 * @param {Object} options 配置
 * @param {Object} options/img 待转换的img DOM元素
 * @param {Number} options/width 转换后的图片宽度，默认为图片自身宽度
 * @param {Number} options/height 转换后的图片高度，默认为图片自身高度
 * @param {Number} options/quality 转换后的图片质量
 * @param {String} options/mimeType 图片的mimeType，默认为image/png
 * @returns {String} 返回DataURL地址
 */
const img2dataUrl = (options) => {
  let result = ''
  let img = options.img || ''
  let width = options.width || img.naturalWidth || img.clientWidth
  let height = options.height || img.naturalHeight || img.clientHeight
  let quality = options.quality || 100
  let mimeType = options.mimeType || 'image/png'
  let canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height
  let ctx = canvas.getContext('2d')
  ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, width, height)
  result = canvas.toDataURL(mimeType, quality / 100)
  return result
}

/**
 * 相对地址转换绝对地址
 * @param {String} url 相对地址
 * @param {String} base 基准地址
 * @returns {String} 绝对地址
 */
const relativeUrl2absoluteUrl = (url, base) => {
  let ele = document.createElement('a')
  ele.href = (base || '') + url
  return ele.href
}

/**
 * arrayBuffer转换为base64
 * @param {ArrayBuffer} data 数据
 * @param {String} mimeType
 * @returns {String} base64
 */
 const arrayBuffer2base64 = (data, mimeType) => {
  data = new Uint8Array(data)
  let text = ''
  for (let i = 0; i < data.byteLength; i++) {
    text += String.fromCharCode(data[i])
  }
  return `data:${mimeType};base64,${btoa(text)}`
}

/**
 * 加载图片数据
 * @param {String/File} param 图片地址或File对象
 * @returns {Object} 返回一个Promise对象
 */
const loadImage = (param) => {
  return new Promise((resolve, reject) => {
    let paramType = (typeof param)
    if (!param) { reject('图片地址不能为空') }
    else if (paramType === 'string') {
      let imgObj = new Image()
      imgObj.onload = () => { resolve(imgObj) }
      imgObj.onerror = () => { reject('图片加载失败') }
      imgObj.setAttribute('crossOrigin', 'anonymous')
      imgObj.src = param
    }
    else if (paramType === 'object') {
      let fr = new FileReader()
      fr.addEventListener('load', () => {
        let imgObj = new Image()
        imgObj.onload = () => { resolve(imgObj) }
        imgObj.onerror = () => { reject('图片加载失败') }
        imgObj.setAttribute('crossOrigin', 'anonymous')
        imgObj.src = fr.result
      })
      fr.readAsDataURL(param)
    }
    else { reject('图片地址无效') }
  })
}

/**
 * 将XML字符串解析为DOM
 * @param {String} xmlStr
 */
const xml2dom = (xmlStr) => {
  if (!xmlStr) return null
  let result = null
  if (window.DOMParser) {
    let oParser = new DOMParser()
    result = oParser.parseFromString(xmlStr, 'text/xml')
  } else {
    result = new ActiveXObject('Microsoft.XMLDOM')
    result.async = false
    result.loadXML(xmlStr)
  }
  return result
}

/**
 * 将XMLDOM转换为XML字符串
 * @param {Object} xmlDom
 */
const dom2xml = (xmlDom) => {
  if (!xmlDom) return ''
  let result = ''
  try {
    let oSerializer = new XMLSerializer()
    result = oSerializer.serializeToString(xmlDom)
  } catch (e) {
    try {
      result = xmlDom.xml
    } catch (e) {
      return ''
    }
  }
  if (result.indexOf('<?xml') === -1) {
    result = `<?xml version='1.0' encoding='utf-8'?>` + result
  }
  return result
}

/**
 * 将ArrayBuffer转换为AudioBuffer
 * @param {ArrayBuffer} arrayBuffer
 * @returns {Promise/AudioBuffer} 返回一个Promise对象，接收值为一个AudioBuffer
 */
const arrayBuffer2audioBuffer = (arrayBuffer) => {
  const audioContext = new AudioContext()
  return audioContext.decodeAudioData(arrayBuffer).then(audioBuffer => {
    resolve(audioBuffer)
  })
}

/**
 * 基于url地址获得 AudioBuffer 的方法
 * @param {String} url
 * @returns {Promise/AudioBuffer} 返回一个Promise对象，接收值为一个AudioBuffer
 */
const url2audioBuffer = (url) => {
  const audioContext = new AudioContext()
  return new Promise((resolve, reject) => {
    fetch(url).then(response => response.arrayBuffer()).then(arrayBuffer => {
      audioContext.decodeAudioData(arrayBuffer).then(audioBuffer => {
        resolve(audioBuffer)
      })
    })
  })
}

/**
 * 将AudioBuffer转换为Wave
 * @param {AudioBuffer} audioBuffer
 * @param {Number} len
 */
const audioBuffer2wave = (audioBuffer, len) => {
  let numOfChan = audioBuffer.numberOfChannels
  len = len || audioBuffer.sampleRate * audioBuffer.duration
  let length = len * numOfChan * 2 + 44
  let buffer = new ArrayBuffer(length)
  let view = new DataView(buffer)
  let channels = [], i, sample, offset = 0, pos = 0
  // write WAVE header
  // "RIFF"
  setUint32(0x46464952)
  // file length - 8
  setUint32(length - 8)
  // "WAVE"
  setUint32(0x45564157)
  // "fmt " chunk
  setUint32(0x20746d66)
  // length = 16
  setUint32(16)
  // PCM (uncompressed)
  setUint16(1)
  setUint16(numOfChan)
  setUint32(audioBuffer.sampleRate)
  // avg. bytes/sec
  setUint32(audioBuffer.sampleRate * 2 * numOfChan)
  // block-align
  setUint16(numOfChan * 2)
  // 16-bit (hardcoded in this demo)
  setUint16(16)
  // "data" - chunk
  setUint32(0x61746164)
  // chunk length
  setUint32(length - pos - 4)
  // write interleaved data
  for(i = 0; i < audioBuffer.numberOfChannels; i++) {
    channels.push(audioBuffer.getChannelData(i))
  }
  while(pos < length) {
    // interleave channels
    for(i = 0; i < numOfChan; i++) {
      // clamp
      sample = Math.max(-1, Math.min(1, channels[i][offset]))
      // scale to 16-bit signed int
      sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0
      // write 16-bit sample
      view.setInt16(pos, sample, true)
      pos += 2
    }
    // next source sample
    offset++
  }
  // create Blob
  return new Blob([buffer], {type: 'audio/wav'})
  function setUint16(data) {
    view.setUint16(pos, data, true)
    pos += 2
  }
  function setUint32(data) {
    view.setUint32(pos, data, true)
    pos += 4
  }
}

/**
 * 拼接音频
 * @param {AudioBuffer[]} AudioBuffer数据数组
 */
const concatAudioBufferList = (audioBufferList) => {
  // AudioContext
  const audioContext = new AudioContext()
  // 最大通道数
  const maxChannelNumber = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.numberOfChannels))
  // 总长度
  const totalLength = audioBufferList.map((buffer) => buffer.length).reduce((lenA, lenB) => lenA + lenB, 0)
  // 创建一个新的 AudioBuffer
  const newAudioBuffer = audioContext.createBuffer(maxChannelNumber, totalLength, audioBufferList[0].sampleRate)
  // 将所有的 AudioBuffer 的数据拷贝到新的 AudioBuffer 中
  let offset = 0
  audioBufferList.forEach((audioBuffer, index) => {
    for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
      newAudioBuffer.getChannelData(channel).set(audioBuffer.getChannelData(channel), offset)
    }
    offset += audioBuffer.length
  })
  return newAudioBuffer
}

/**
 * 合并音频
 * @param {AudioBuffer[]} AudioBuffer数据数组
 */
const mergeAudioBufferList = (audioBufferList) => {
  // AudioContext
  const audioContext = new AudioContext()
  // 最大播放时长
  const maxDuration = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.duration))
  // 最大通道数
  const maxChannelNumber = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.numberOfChannels))
  // 创建一个新的 AudioBuffer
  const newAudioBuffer = audioContext.createBuffer(maxChannelNumber, audioBufferList[0].sampleRate * maxDuration, audioBufferList[0].sampleRate)
  // 将所有的 AudioBuffer 的数据合并到新的 AudioBuffer 中
  audioBufferList.forEach((audioBuffer, index) => {
    for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
      const outputData = newAudioBuffer.getChannelData(channel)
      const bufferData = audioBuffer.getChannelData(channel)
      for (let i = audioBuffer.getChannelData(channel).length - 1; i >= 0; i--) {
        outputData[i] += bufferData[i]
      }
      newAudioBuffer.getChannelData(channel).set(outputData)
    }
  })
  return newAudioBuffer
}

/**
 * 音频裁剪
 * @param {AudioBuffer} AudioBuffer数据
 * @param {Number} startOffset 起始点的秒数
 * @param {Number} endOffset 截止点的秒数
 */
const cutAudioBuffer = (audioBuffer, startOffset, endOffset) => {
  // 声道数量和采样率
  const channelNumber = audioBuffer.numberOfChannels
  const rate = audioBuffer.sampleRate
  // 截取第*秒到第*秒
  startOffset = Number(startOffset) || 0
  endOffset = rate * Math.min(Number(endOffset), audioBuffer.duration)
  // 截取区间对应的帧数
  let frameCount = endOffset - startOffset
  // 创建同样采用率、同样声道数量，长度是*秒的空的AudioBuffer
  let newAudioBuffer = new AudioContext().createBuffer(channelNumber, endOffset - startOffset, rate)
  // 创建临时的Array存放复制的buffer数据
  let anotherArray = new Float32Array(frameCount)
  // 声道的数据的复制和写入
  let offset = 0
  for (let i = 0; i < channelNumber; i++) {
    audioBuffer.copyFromChannel(anotherArray, i, startOffset)
    newAudioBuffer.copyToChannel(anotherArray, i, offset)
  }
  return newAudioBuffer
}

/**
 * 按基准大小缩放文档视口
 * @param {Number} baseSize
 */
const zoomScreenByBaseSize = (baseSize) => {
  if (!baseSize) return
  let scale = window.screen.width / baseSize
  let meta = document.createElement('meta')
  meta.name = 'viewport'
  meta.content = `initial-scale=${scale},minimum-scale=${scale},maximum-scale=1,user-scalable=yes`
  if (document.head) {
    document.head.appendChild(meta)
  }
}

/**
 * 获取Url地址中参数
 * @param {String} url 可有可无
 */
const getUrlParams = (url) => {
  url = url || location.href
  let result = {}
  let search = url.split('?')[1] || ''
  let params = search.split('&')
  params.forEach(v => {
    let [key, val] = v.split('=')
    key && (result[key] = val || '')
  })
  return result
}

/**
 * 获取本机IP地址
 * 前置条件: 在Chrome浏览器“chrome://flags/”中搜索“#enable-webrtc-hide-local-ips-with-mdns”，将该配置设置为 disabled，重启生效后方可
 */
const getIpAddress = (callback) => {
  let MyPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection
  let pc = new MyPeerConnection({ iceServers: [] })
  let noop = () => {}
  let localIPs = {}
  let ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g
  let iterateIP = (ip) => {
    if (!localIPs[ip]) {
      callback && callback(ip)
    }
    localIPs[ip] = true
  }
  pc.createDataChannel('')
  pc.createOffer().then((sdp) => {
    sdp.sdp.split('\n').forEach(function (line) {
      if (line.indexOf('candidate') < 0) return
      line.match(ipRegex).forEach(iterateIP)
    })
    pc.setLocalDescription(sdp, noop, noop)
  }).catch((reason) => {
    console.log('getIpAddress/createOffer:', reason)
  })
  pc.onicecandidate = (ice) => {
    if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return
    ice.candidate.candidate.match(ipRegex).forEach(iterateIP)
  }
}

/**
 * 获取系统类型
 * @returns {String} 返回系统类型(Windows/UNIX/Linux/MacOS/Android/iOS)
 */
const getSystemType = () => {
	let OSName = 'unknown'
	if(navigator.appVersion.indexOf('Win') > -1) OSName = 'Windows'
	if(navigator.appVersion.indexOf('X11') > -1) OSName = 'UNIX'
	if(navigator.appVersion.indexOf('Linux') > -1) OSName = 'Linux'
	if(navigator.appVersion.indexOf('Mac') > -1) OSName = 'MacOS'
	if(navigator.userAgent.match(/Android/i)) OSName = 'Android'
	if(navigator.userAgent.match(/iPhone|iPad|iPod/i)) OSName = 'iOS'
	return OSName
}

/**
 * 获取视频缩略图
 * @param {Object} options 参数配置
 * @param {Array} options/url 视频url
 * @param {Array} options/timeStart 起始时间
 * @param {Array} options/timeInterval 时间间隔
 * @param {Array} options/onLoading 开始加载
 * @param {Array} options/onLoaded 加载完成
 * @param {String} options/onFinish 处理完毕
 */
const getVideoThumb = (options) => {
  options = options || {}
  let url = options.url || ''
  let timeStart = parseFloat(options.timeStart) || 0.1
  let timeInterval = parseFloat(options.timeInterval) || 1
  let onLoading = options.onLoading || (() => {})
  let onLoaded = options.onLoaded || (() => {})
  let onFinish = options.onFinish || ((arr) => {})
  if (!url || typeof url !== 'string') {
    return
  }
  // 基于视频元素绘制缩略图，而非解码视频
  const video = document.createElement('video')
  // 静音
  video.muted = true
  // 绘制缩略图的canvas画布元素
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d', { willReadFrequently: true })
  // 绘制缩略图的标志量
  let isTimeUpdated = false
  // 1. 视频事件/获取视频尺寸
  video.addEventListener('loadedmetadata', () => {
    canvas.width = video.videoWidth
    canvas.height = video.videoHeight
    // 开始执行绘制
    draw()
  })
  // 2. 视频事件/触发绘制监控
  video.addEventListener('timeupdate', () => {
    isTimeUpdated = true
  })
  // 获取视频数据
  onLoading()
  // 请求视频地址，如果是本地文件，直接执行
  if (/^blob:|base64,/i.test(url)) {
    video.src = url
  } else {
    fetch(url).then(res => res.blob()).then(blob => {
      onLoaded()
      video.src = URL.createObjectURL(blob)
    })
  }
  // 绘制方法
  const draw = () => {
    const arrThumb = []
    const duration = video.duration
    let seekTime = timeStart
    const loop = () => {
      if (isTimeUpdated) {
        context.clearRect(0, 0, canvas.width, canvas.height)
        context.drawImage(video, 0, 0, canvas.width, canvas.height)
        canvas.toBlob(blob => {
          arrThumb.push(URL.createObjectURL(blob))
          seekTime += timeInterval
          if (seekTime > duration) {
            onFinish(arrThumb)
            return
          }
          step()
        }, 'image/jpeg')
        return
      }
      // 监控状态
      requestAnimationFrame(loop)
    }
    // 逐步绘制，因为currentTime修改生效是异步的
    const step = () => {
      isTimeUpdated = false
      video.currentTime = seekTime
      loop()
    }
    step()
  }
}

/**
 * 事件绑定&触发&解除
 * @param {String} eventName 事件名称
 * @param {Function} callback 回调函数
 * @param {Boolean} isBubbling 是否冒泡
 * @param {Boolean} cancelable 是否阻止浏览器的默认行为
 * @param {Object} argument 自定义传参
 * 示例 myEvent.dispatchEvent('召唤兵器', {name: 'jstools-common'})
 */
const myEvent = {
  dispatchEvent: (eventName, argument, isBubbling, cancelable) => {
    let event = document.createEvent('HTMLEvents')
    event.initEvent(eventName, isBubbling, cancelable)
    Object.assign(event, argument)
    document.dispatchEvent(event)
    return event.result
  },
  addEventListener: (eventName, callback) => {
    document.addEventListener(eventName, callback)
  },
  removeEventListener: (eventName, callback) => {
    document.removeEventListener(eventName, callback)
  }
}

/**
 * Cookie操作
 * @param {String} key 参数名称
 * @param {String} value 参数值
 * @param {Object} options 其它参数配置expires|path|domain|secure
 */
const myCookie = {
  whiteList: ['expires', 'path', 'domain', 'secure'],
  getAll: function () {
    let result = {}
    if (document.cookie) {
      let arr = document.cookie.split(';')
      arr.forEach(v => {
        v = (v || '').replace(/^\s+|\s+$/g, '')
        let index = v.indexOf('=')
        if (index > -1) {
          result[v.substring(0, index)] = decodeURIComponent(v.substring(index + 1))
        }
      })
    }
    return result
  },
  getItem: function (key) {
    key = key || ''
    let data = this.getAll()
    return data[key] || ''
  },
  setItem: function (key, value, options) {
    options = options || {}
    let data = []
    data.push(`${key}=${encodeURIComponent(value || '')}`)
    // expires
    if (value === null) options.expires = -1
    let expires = ''
    let expires_type = toolsPublic.typeOf(options.expires).toLowerCase()
    if (options.expires && (expires_type === 'number' || options.expires.toUTCString)) {
      let date = ''
      if (expires_type === 'number') {
        date = new Date()
        date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000)
      } else {
        date = options.expires
      }
      expires = date.toUTCString()
    }
    data.push(`expires=${expires}`)
    // path|domain|secure
    data.push(options.path ? `path=${options.path}` : '') // 设置路径
    data.push(options.domain ? `path=${options.domain}` : '') // 设置域
    data.push(options.secure ? `secure` : '') // 设置安全措施，为 true 则直接设置，否则为空
    document.cookie = data.join(';')
  },
  removeItem: function (key) {
    this.setItem(key, null)
  },
  clear: function () {
    let _this = this
    let data = _this.getAll()
    Object.keys(data).forEach(k => {
      _this.removeItem(k)
    })
  }
}

/**
 * 网页截图并保存为PDF文件
 * @param {Object} options 参数配置
 * @param {Array} options/catalogues 封面对应的dom元素数组
 * @param {Array} options/contents 内容页对应的dom元素数组
 * @param {String} options/clipClass 可裁切元素的类名
 * @param {String} options/flipClass 需另起一页绘制的元素的类名
 * @param {String} options/fileName 要保存的截图名称
 * @param {Boolean} options/useCORS htmlCanvas配置参数（默认为false）
 * @param {String} options/mimeType 页面生成图片的媒体类型（默认为image/jpg）
 * @param {String} options/orientation 页面采用横向还是纵向（默认为横向landscape）
 * @param {Number} options/offsetX 页面X轴偏移量（默认值undefined）
 * @param {Number} options/offsetY 页面Y轴偏移量（默认值undefined）
 * @param {Number} options/paperWidth 页面宽度（默认参考A4纸，取值297）
 * @param {Number} options/paperHeight 页面高度（默认参考A4纸，取值210）
 * @param {Number} options/availWidth 有效宽度（默认参考A4纸，取值287）
 * @param {Number} options/availHeight 有效高度（默认参考A4纸，取值185）
 * @param {Number} options/pageStartTop 页面内容纵向开始位置（默认参考A4纸，取值10）
 * @param {Number} options/pageStartLeft 页面内容横向开始位置（默认参考A4纸，取值5）
 * @param {Number} options/pageNumTop 页码纵向位置（默认参考A4纸，取值280）
 * @param {Number} options/pageNumLeft 页码横向位置（默认参考A4纸，取值205）
 * @param {Number} options/fontSize 文本字号大小（默认12）
 */
const screenshot2pdf = (options = {}) => {
  options = options || {}
  let html2canvas = options.html2canvas || window.html2canvas
  let jsPDF = options.jsPDF || (window.jspdf || {}).jsPDF
  let catalogues = [...(options.catalogues || [])]
  let contents = [...(options.contents || [])]
  let clipClass = options.clipClass || ''
  let flipClass = options.flipClass || ''
  let orientation = options.orientation || 'landscape'
  let mimeType = options.mimeType || 'image/jpg'
  let fileName = options.fileName || 'screenshot'
  let useCORS = !!options.useCORS || undefined
  let offsetX = options.offsetX
  let offsetY = options.offsetY
  let paperWidth = options.paperWidth || 297
  let paperHeight = options.paperHeight || 210
  let availWidth = options.availWidth || 287
  let availHeight = options.availHeight || 185
  let pageStartTop = options.pageStartTop || 10
  let pageStartLeft = options.pageStartLeft || 5
  let pageNumTop = options.pageNumTop || 280
  let pageNumLeft = options.pageNumLeft || 205
  let fontSize = options.fontSize || 12
  if (!html2canvas) return Promise.reject('need input plugin method: html2canvas')
  if (!jsPDF) return Promise.reject('need input plugin method: jsPDF')
  if (!catalogues.length && !contents.length) return Promise.reject('tasks is empty')
  let _scrollTop = document.querySelector('html').scrollTop // 记录滚动条位置，程序执行完成后恢复
  let _scale = [...catalogues, ...contents][0].getBoundingClientRect().width / availWidth // dom文档和纸张之间的比例
  let _pageMaxHeight = _scale * availHeight // 每一页最大容纳的dom高度
  let _pageUsedHeight = 0 // 当前页面已经绘制的图像高度
  let _offset = 0
  let cataloguesPageList = [[]]
  let contentsPageList = [[]]
  let cataloguesTaskList = []
  let contentsTaskList = []
  function doms2pages (doms) {
    let pageList = [[]]
    let taskList = []
    doms.forEach((v, i) => {
      let vInfo = v.getBoundingClientRect()
      let {width, height} = vInfo
      let canClip = clipClass ? [...v.classList].includes(clipClass) : false
      let needFlip = flipClass ? [...v.classList].includes(flipClass) : false
      if (canClip && height > _pageMaxHeight) {
        let __ht = 0 // 已绘制高度
        let __hb = height // 待绘制高度
        let __s = _pageMaxHeight - _pageUsedHeight // 当前页面富余量
        let __c = 0 // 裁切第几次
        let __h = __s // 裁切高度
        while (__hb > 0) {
          // html2canvas
          if (__c === 0) {
            __h = needFlip ? _pageMaxHeight : __s
          } else {
            __h = Math.min(__hb, _pageMaxHeight)
          }
          taskList.push(
            html2canvas(v, {
              width, height: __h,
              y: (isNaN(Number(offsetY)) ? (vInfo.y || vInfo.top) : offsetY) + __ht,
              x: isNaN(Number(offsetX)) ? (vInfo.x || vInfo.left) : offsetX,
              _scale, allowTaint: true, useCORS
            })
          )
          // page
          if (_pageUsedHeight + __h <= _pageMaxHeight) {
            pageList[pageList.length - 1].push(i + _offset)
            _pageUsedHeight += __h
          } else if (_pageUsedHeight === 0) {
            pageList[pageList.length - 1].push(i + _offset)
            pageList[pageList.length] = []
          } else {
            pageList[pageList.length] = []
            pageList[pageList.length - 1].push(i + _offset)
            _pageUsedHeight = __h
          }
          if (__hb > _pageMaxHeight) {
            __c++
            _offset++
          }
          __ht += __h
          __hb -= __h
        }
      } else {
        // html2canvas
        taskList.push(
          html2canvas(v, {
            width, height,
            y: isNaN(Number(offsetY)) ? (vInfo.y || vInfo.top) : offsetY,
            x: isNaN(Number(offsetX)) ? (vInfo.x || vInfo.left) : offsetX,
            _scale, allowTaint: true, useCORS
          })
        )
        // page
        if (_pageUsedHeight + height <= _pageMaxHeight) {
          pageList[pageList.length - 1].push(i + _offset)
          _pageUsedHeight += height
        } else if (_pageUsedHeight === 0) {
          pageList[pageList.length - 1].push(i + _offset)
          pageList[pageList.length] = []
        } else {
          pageList[pageList.length] = []
          pageList[pageList.length - 1].push(i + _offset)
          _pageUsedHeight = height
        }
      }
    })
    return {pageList, taskList}
  }
  document.querySelector('html').scrollTop = 0 // 截图前需将滚动条重置为0
  let _resultCatalogues = doms2pages(catalogues)
  cataloguesPageList = _resultCatalogues.pageList
  cataloguesTaskList = _resultCatalogues.taskList
  let _resultContents = doms2pages(contents)
  contentsPageList = _resultContents.pageList
  contentsTaskList = _resultContents.taskList
  return Promise.all([...cataloguesTaskList, ...contentsTaskList]).then((canvasArr) => {
    let canvasArrCatalogues = canvasArr.slice(0, cataloguesTaskList.length)
    let canvasArrContents = canvasArr.slice(cataloguesTaskList.length)
    let doc = new jsPDF(orientation)
    let docIndex = 0
    doc.setFontSize(fontSize)
    /**
     * PDF页面组装
     * @param {Array} pageList 散装页面列表
     * @param {Number} _x 页面内容横向开始位置
     * @param {Number} _y 页面内容纵向开始位置
     * @param {Boolean} isCoverPage 是否为封面
     */
    function pagesPack (pageList, canvasList, isCoverPage) {
      pageList.forEach((v, i) => {
        if (v.length === 0) return
        if (docIndex > 0) doc.addPage()
        let _x = pageStartLeft
        let _y = pageStartTop
        let _w = availWidth
        if (isCoverPage) {
          _x = _y = 0
          _w = paperWidth
        }
        v.forEach(val => {
          let canvas = canvasList[val]
          let _r = canvas.width / _w
          let _h = canvas.height / (_r || 1)
          doc.addImage(canvas.toDataURL(mimeType), 'JPEG', _x, _y, _w, _h)
          _y += _h
        })
        if (!isCoverPage) {
          doc.text(`${i + 1} / ${pageList.length}`, pageNumTop, pageNumLeft)
        }
        console.log(docIndex, 'pageList', v, i, pageList.length)
        docIndex++
      })
    }
    pagesPack(cataloguesPageList, canvasArrCatalogues, true)
    pagesPack(contentsPageList, canvasArrContents, false)
    doc.save(`${fileName}.pdf`)
    document.querySelector('html').scrollTop = _scrollTop // 截图后将滚动条恢复
  })
}

/**
 * 网页截图并保存为PDF文件
 * @param {Object} options 参数配置
 * @param {Array} options/elements 内容页对应的dom元素数组
 * @param {String} options/coverClass 封面元素的类名
 * @param {String} options/clipClass 可裁切元素的类名
 * @param {String} options/flipClass 需另起一页绘制的元素的类名
 * @param {String} options/fileName 要保存的截图名称
 * @param {Boolean} options/useCORS htmlCanvas配置参数（默认为false）
 * @param {String} options/mimeType 页面生成图片的媒体类型（默认为image/jpg）
 * @param {String} options/orientation 页面采用横向还是纵向（默认为横向landscape）
 * @param {Number} options/offsetX 页面X轴偏移量（默认值undefined）
 * @param {Number} options/offsetY 页面Y轴偏移量（默认值undefined）
 * @param {Number} options/paperWidth 页面宽度（默认参考A4纸，取值297）
 * @param {Number} options/paperHeight 页面高度（默认参考A4纸，取值210）
 * @param {Number} options/availWidth 有效宽度（默认参考A4纸，取值287）
 * @param {Number} options/availHeight 有效高度（默认参考A4纸，取值185）
 * @param {Number} options/pageStartTop 页面内容纵向开始位置（默认参考A4纸，取值10）
 * @param {Number} options/pageStartLeft 页面内容横向开始位置（默认参考A4纸，取值5）
 * @param {Number} options/pageNumTop 页码纵向位置（默认参考A4纸，取值280）
 * @param {Number} options/pageNumLeft 页码横向位置（默认参考A4纸，取值205）
 * @param {Number} options/fontSize 文本字号大小（默认12）
 */
const screenshot2pdfV2 = (options = {}) => {
  options = options || {}
  let html2canvas = options.html2canvas || window.html2canvas
  let jsPDF = options.jsPDF || (window.jspdf || {}).jsPDF
  let elements = [...(options.elements || [])]
  let clipClass = options.clipClass || ''
  let flipClass = options.flipClass || ''
  let coverClass = options.coverClass || ''
  let mimeType = options.mimeType || 'image/jpg'
  let orientation = options.orientation || 'landscape'
  let fileName = options.fileName || 'screenshot'
  let useCORS = !!options.useCORS || undefined
  let offsetX = options.offsetX
  let offsetY = options.offsetY
  let paperWidth = options.paperWidth || 297
  let paperHeight = options.paperHeight || 210
  let availWidth = options.availWidth || 287
  let availHeight = options.availHeight || 185
  let pageStartTop = options.pageStartTop || 10
  let pageStartLeft = options.pageStartLeft || 5
  let pageNumTop = options.pageNumTop || 280
  let pageNumLeft = options.pageNumLeft || 205
  let fontSize = options.fontSize || 12
  if (!html2canvas) return Promise.reject('need input plugin method: html2canvas')
  if (!jsPDF) return Promise.reject('need input plugin method: jsPDF')
  if (!elements.length) return Promise.reject('tasks is empty')
  return new Promise((resolve, reject) => {
    let _pageNum = 0
    let _scrollTop = document.querySelector('html').scrollTop // 记录滚动条位置，程序执行完成后恢复
    let _scale = elements[0].getBoundingClientRect().width / availWidth // dom文档和纸张之间的比例
    let _pageMaxHeight = Math.floor(_scale * availHeight) // 每一页最大容纳的dom高度
    let _pageUsedHeight = 0 // 当前页面已经绘制的图像高度
    let _elementsActiveIndex = 0
    let _elementsTotal = elements.length
    let _elementsOffset = 0
    let doc = new jsPDF(orientation)
    let docIndex = 0
    doc.setFontSize(fontSize)
    document.querySelector('html').scrollTop = 0
    let _nodeList = [...(document.querySelectorAll(`[screenshot2pdf]`) || [])]
    _nodeList.forEach(v => v.removeAttribute('screenshot2pdf'))
    elements.forEach((v, i) => v.setAttribute('screenshot2pdf', `screenshot2pdf_${i}`))
    // 元素处理
    function elementHandler() {
      let _element = document.querySelector(`[screenshot2pdf='screenshot2pdf_${_elementsActiveIndex}']`)
      let _elementInfo = _element.getBoundingClientRect()
      let _classList = [..._element.classList]
      let _canClip = clipClass ? _classList.includes(clipClass) : false
      let _needFlip = flipClass ? _classList.includes(flipClass) : false
      let _isCover = coverClass ? _classList.includes(coverClass) : false
      let _pageSurplusHeight = _pageMaxHeight - _pageUsedHeight // 当前页面富余量
      let _taskItem = null
      let _taskInfo = null
      // 预处理pdf信息
      if (_pageNum > docIndex) {
        doc.text(`NO. ${docIndex + 1}`, pageNumTop, pageNumLeft)
        docIndex++
        doc.addPage()
      }
      if (_isCover) {
        _taskInfo = {
          element: _element, pageNum: _pageNum, width: _elementInfo.width, height: 0,
          x: isNaN(Number(offsetX)) ? (_elementInfo.x || _elementInfo.left) : offsetX,
          y: isNaN(Number(offsetY)) ? (_elementInfo.y || _elementInfo.top) : offsetY,
          pdfLeft: 0, pdfTop: 0, pdfWidth: paperWidth, isCover: true
        }
        _taskItem = elementSliceHandler(_taskInfo)
        _pageNum++
        _pageUsedHeight = 0
        _pageSurplusHeight = _pageMaxHeight
        _elementsOffset = 0
        _elementsActiveIndex++
      } else {
        _taskInfo = {
          element: _element, pageNum: _pageNum, width: _elementInfo.width, height: 0,
          x: isNaN(Number(offsetX)) ? (_elementInfo.x || _elementInfo.left) : offsetX,
          y: (isNaN(Number(offsetY)) ? (_elementInfo.y || _elementInfo.top) : offsetY) + _elementsOffset,
          pdfLeft: pageStartLeft, pdfTop: pageStartTop + _pageUsedHeight / _scale, pdfWidth: availWidth
        }
        if (_pageSurplusHeight <= 0 || (_pageUsedHeight > 0 && _isCover) ||
          (_pageUsedHeight > 0 && _elementsOffset === 0 && (_needFlip || (!_canClip && (_elementInfo.height > _pageSurplusHeight))))) {
          _pageNum++
          _pageUsedHeight = 0
          _pageSurplusHeight = _pageMaxHeight
        }
        let _todoHeight = _elementInfo.height - _elementsOffset // 待处理的高度
        if (_todoHeight <= _pageSurplusHeight) {
          _taskInfo.height = _todoHeight
          _pageUsedHeight += _todoHeight
          _pageSurplusHeight = _pageMaxHeight - _pageUsedHeight
          _elementsOffset = 0
          _elementsActiveIndex++
        } else {
          _taskInfo.height = _pageSurplusHeight
          _elementsOffset += _taskInfo.height
          _pageNum++
          _pageUsedHeight = 0
          _pageSurplusHeight = _pageMaxHeight
        }
        _taskItem = elementSliceHandler(_taskInfo)
      }
      _taskItem.catch(err => {
        if (_elementsActiveIndex >= _elementsTotal) reject(err)
      }).finally(() => {
        if (_elementsActiveIndex >= _elementsTotal) {
          if (!_taskInfo.isCover) {
            doc.text(`NO. ${docIndex + 1}`, pageNumTop, pageNumLeft)
          }
          doc.save(`${fileName}.pdf`)
          document.querySelector('html').scrollTop = _scrollTop // 截图后将滚动条恢复
          resolve()
        } else {
          elementHandler()
        }
      })
    }
    // 绘制图片&入栈PDF
    function elementSliceHandler (item) {
      return html2canvas(item.element, {
        width: item.width, height: item.height, x: item.x, y: item.y,
        scale: _scale, allowTaint: true, useCORS
      }).then((canvas) => {
        let _rate = canvas.width / item.pdfWidth
        let _pdfHeight = canvas.height / (_rate || 1)
        doc.addImage(canvas.toDataURL(mimeType), 'JPEG', item.pdfLeft, item.pdfTop, item.pdfWidth, _pdfHeight)
      })
    }
    elementHandler()
  })
}

/**
 * 写入内容到剪贴板
 * @param {String} text 要写入剪贴板的文本
 * @returns {Promise} 返回一个Promise对象
 */
const writeClipboard = (text) => {
  return new Promise((resolve, reject) => {
    // 优先使用 Clipboard API
    if (navigator.clipboard && window.isSecureContext) {
      navigator.clipboard.writeText(text).then(() => {
        resolve('success')
      }).catch(err => {
        reject(err)
      })
    } else {
      // 降级使用 document.execCommand
      try {
        const textarea = document.createElement('textarea')
        textarea.value = text
        textarea.style.position = 'fixed'
        textarea.style.opacity = '0'
        document.body.appendChild(textarea)
        textarea.select()
        const result = document.execCommand('copy')
        document.body.removeChild(textarea)
        if (result) {
          resolve('success')
        } else {
          reject(new Error('Copy command failed'))
        }
      } catch (err) {
        reject(err)
      }
    }
  })
}

/**
 * 读取剪贴板内容
 * @returns {Promise<string>} 返回一个Promise对象，resolve值为剪贴板中的文本内容
 */
const readClipboard = () => {
  return new Promise((resolve, reject) => {
    // 优先使用 Clipboard API
    if (navigator.clipboard && window.isSecureContext) {
      navigator.clipboard.readText().then(text => {
        resolve(text)
      }).catch(err => {
        reject(err)
      })
    } else {
      reject(new Error('Clipboard API not available'))
    }
  })
}

let toolsWeb = {
  ...toolsPublic,
  ...polyfill,
  ...media,
  writeClipboard,
  readClipboard,
  downloadFileByA,
  downloadFileByAV2,
  downloadFileByIframe,
  downloadFileByForm,
  downloadFile,
  saveFileByBlob,
  searchVueComponent,
  formatNumber,
  runPrefixMethod,
  onPrefixEvent,
  offPrefixEvent,
  parseURLByA,
  scrollTop,
  isFullscreen,
  isFullscreenEnabled,
  isWeixinBrowser,
  isMobileBrowser,
  screenIsLandscape,
  screenIsPortrait,
  base64Decode,
  base642utf8,
  utf82base64,
  dataUrl2File,
  File2dataUrl,
  img2dataUrl,
  relativeUrl2absoluteUrl,
  arrayBuffer2base64,
  loadImage,
  xml2dom,
  dom2xml,
  arrayBuffer2audioBuffer,
  url2audioBuffer,
  audioBuffer2wave,
  concatAudioBufferList,
  mergeAudioBufferList,
  cutAudioBuffer,
  zoomScreenByBaseSize,
  getUrlParams,
  getIpAddress,
  getSystemType,
  getVideoThumb,
  myEvent,
  myCookie,
  screenshot2pdf,
  screenshot2pdfV2
}

module.exports = (jstools) => {
  for (let k in toolsWeb) {
      jstools[k] = toolsWeb[k]
  }
}
